﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace GA_Scheduler
{
    public class AlgorithmDBStateEventArgs : EventArgs
    {
        /// <summary>
        /// State of algorithm
        /// </summary>
        public AlgorithmState State
        {
            get;
            private set;
        }

        public AlgorithmDBStateEventArgs(AlgorithmState st)
        {
            State = st;
        }

    }

    public class AlgorithmDBEventArgs : EventArgs
    {
        /// <summary>
        /// Gets best generated schedule
        /// </summary>
        public Schedule BestSchedule
        {
            get;
            private set;
        }

        /// <summary>
        /// Population in which scheule was generated
        /// </summary>
        public int Population
        {
            get;
            private set;

            
        }


        public AlgorithmDBEventArgs(Schedule _chrom, int population)
        {
            BestSchedule = _chrom;
            Population = population;
        }
    }

    public class AlgorithmDB
    {
        /// <summary>
        /// List of sorted best chromosomes.
        /// </summary>
        private List<Schedule> BestChromosomes;

        /// <summary>
        /// All chromosomes
        /// </summary>
        private List<Schedule> chromosomes;
        private int currentGeneration;
        private AlgorithmState state;
        private Schedule BestSchedule;

        

        /// <summary>
        /// Occurs when state of algorithm is changed
        /// </summary>
        public event EventHandler<AlgorithmDBStateEventArgs> StateChanged;

        /// <summary>
        /// Occurs when new best schedule was generated
        /// </summary>
        public event EventHandler<AlgorithmDBEventArgs> NewBestSchedule;

        /// <summary>
        /// Initialize new instanse of algorithm
        /// </summary>
        public AlgorithmDB()
        {
            chromosomes = new List<Schedule>(); ;
            BestChromosomes = new List<Schedule>();

            for (int i = 0; i < GA_Scheduler.Properties.Settings.Default.NumberOfChromosomes; i++)
            {
                chromosomes.Add(null);
            }

        }

        public void Stop()
        {
            OnStateChanged(AlgorithmState.AS_USER_STOPED);
        }

        /// <summary>
        /// Trying to add chromosome to the best chromosomes if it really is better than others
        /// </summary>
        /// <param name="newBest">New chromosome which can be add to best. Will checked.</param>
        private void AddToBest(Schedule newBest)
        {
            bool added = false;
            for (int i = 0; i < GA_Scheduler.Properties.Settings.Default.CountBestChromosomes; i++)
            {
                if (BestChromosomes.Count <= i)
                {
                    BestChromosomes.Add(newBest);
                    added = true;
                    break;
                }

                if (BestChromosomes[i].Fitness < newBest.Fitness)
                {
                    BestChromosomes[i] = newBest;
                    added = true;
                    break;
                }
            }

            if (!added)
                return;

            BestChromosomes = (from chr in BestChromosomes
                              orderby chr.Fitness descending
                              select chr).ToList();
        }

        /// <summary>
        /// Gets best schedule which was generated
        /// </summary>
        public Schedule BestChromosome
        {
            get 
            {
                if (BestChromosomes.Count == 0)
                    return null;

                return BestChromosomes[0]; 
            }
        }

        /// <summary>
        /// Starts generation. Use BestChromosome property to get a best schedule
        /// </summary>
        public void Start()
        {
            if (state == AlgorithmState.AS_RUNNING)
                return;

            OnStateChanged(AlgorithmState.AS_RUNNING);

            currentGeneration = 0;

            for (int i = 0; i < GA_Scheduler.Properties.Settings.Default.NumberOfChromosomes; i++)
            {
                chromosomes[i] = Schedule.GetFromPrototype();
                AddToBest(chromosomes[i]);
            }

            BestSchedule = BestChromosome;
            OnNewBestChromosome(BestSchedule, currentGeneration);

            while (true)
            {
                currentGeneration++;

                if (currentGeneration % 100 == 0)
                {
                    Configuration.Instance.RefreshRandom();
                }

                // User or program stopped algorithm
                if (state != AlgorithmState.AS_RUNNING)
                    break;

                for (int j = 0; j < GA_Scheduler.Properties.Settings.Default.ReplaceByGeneration; j++)
                {
                    int pos1 = rand() % chromosomes.Count;
                    int pos2 = rand() % chromosomes.Count;

                    Schedule sch1 = chromosomes[pos1];
                    Schedule sch2 = chromosomes[pos2];

                    var crossovedSch = sch1.Crossover(sch2);
                    if (crossovedSch != null)
                    {
                        crossovedSch.Mutation();

                        int rNumber = 0;
                        do
                        {
                            // select chromosome for replacement randomly
                            rNumber = rand() % chromosomes.Count;
                        }
                        while (IsInBest(chromosomes[rNumber])); // protect best chromosomes from replacement

                        chromosomes[rNumber] = crossovedSch;
                        AddToBest(crossovedSch);
                    }
                }

                if (BestChromosome.Fitness > BestSchedule.Fitness)
                {
                    BestSchedule = BestChromosome;
                    OnNewBestChromosome(BestSchedule, currentGeneration);
                }

                if(BestSchedule.Fitness >= 1)
                {
                    OnStateChanged(AlgorithmState.AS_CRITERIA_STOPPED);
                }
                
            }
        }

        /// <summary>
        /// Checks when best chromosomes contain this chromosome
        /// </summary>
        /// <param name="sch">Chromosme that should be checked</param>
        /// <returns>True if contains</returns>
        private bool IsInBest(Schedule sch)
        {
            var exist = (from s in BestChromosomes
                    where s == sch
                    select s).FirstOrDefault();
            return exist != null;
        }

        /// <summary>
        /// Helper function for get random number
        /// </summary>
        /// <returns></returns>
        private static int rand()
        {
            return Configuration.Instance.Rand.Next();
        }

        protected void OnStateChanged(AlgorithmState newState)
        {
            state = newState;

            if (StateChanged != null)
                StateChanged(this, new AlgorithmDBStateEventArgs(newState));
        }

        protected void OnNewBestChromosome(Schedule bchrom, int pop)
        {
            if (NewBestSchedule != null)
                NewBestSchedule(this, new AlgorithmDBEventArgs(bchrom, pop));
        }
    }
}
